#include "D3D10Graphics.h"
#include "TgaLoader.h"

namespace GenBen {

D3D10Texture::D3D10Texture(ID3D10Device* device, const string& filename)
{
	byte* texture_data = NULL;
	loadTga(filename, width, height, (void**)&texture_data);
	ASSERT(texture_data);

	D3D10_TEXTURE2D_DESC desc;
	desc.Width = width;
	desc.Height = height;
	desc.MipLevels = 1;
	desc.ArraySize = 1;
	desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	desc.SampleDesc.Count = 1;
	desc.SampleDesc.Quality = 0;
	desc.Usage = D3D10_USAGE_DYNAMIC;
	desc.BindFlags = D3D10_BIND_SHADER_RESOURCE;
	desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
	desc.MiscFlags = 0;

	ID3D10Texture2D* texture = NULL;
	D3D10_ERR(device->CreateTexture2D(&desc, NULL, &texture));

	D3D10_MAPPED_TEXTURE2D mapped_tex;
	D3D10_ERR(texture->Map(D3D10CalcSubresource(0, 0, 1), D3D10_MAP_WRITE_DISCARD, 0, &mapped_tex));
	byte* texels = (byte*)mapped_tex.pData;
	for(uint row = 0; row < desc.Height; row++ )
	{
		uint row_start = row * mapped_tex.RowPitch;
		for(uint col = 0; col < desc.Width; col++ )
		{
			uint col_start = col * 4;
			texels[row_start + col_start + 0] = texture_data[((height-row-1) * width + col)*4]; 
			texels[row_start + col_start + 1] = texture_data[((height-row-1) * width + col)*4 + 1]; 
			texels[row_start + col_start + 2] = texture_data[((height-row-1) * width + col)*4 + 2]; 
			texels[row_start + col_start + 3] = texture_data[((height-row-1) * width + col)*4 + 3]; 
		}
	}
	texture->Unmap(D3D10CalcSubresource(0, 0, 1));


	D3D10_ERR(device->CreateShaderResourceView(texture, NULL, &shader_resource_view));

	texture->Release();

	is_released = false;
}

D3D10Texture::~D3D10Texture()
{
	if(!is_released)
	{
		shader_resource_view->Release();
		is_released = true;
	}
}

uint D3D10Texture::getWidth()
{
	return width;
}

uint D3D10Texture::getHeight()
{
	return height;
}

void D3D10Texture::release()
{
	if(!is_released)
	{
		shader_resource_view->Release();
		is_released = true;
	}
	else
	{
		criticalError("Texture already released");
	}
}

bool D3D10Texture::isReleased()
{
	return is_released;
}

ID3D10ShaderResourceView* D3D10Texture::getResourceView()
{
	return shader_resource_view;
}

D3D10Graphics::D3D10Graphics(HWND window, uint width, uint height, uint scale, uint refresh_rate, bool fullscreen, bool lcd)
{
	ASSERT(window);
	ASSERT(width >= 320 && width <= 1600);
	ASSERT(height >= 240 && height <= 1200);
	ASSERT(scale >= 1 && scale <= 4);
	//ASSERT(refresh_rate >= 60 && refresh_rate <= 85);

	uint deviceFlags = 0;
#ifdef _DEBUG
	deviceFlags |= D3D10_CREATE_DEVICE_DEBUG;
#endif

	this->width = width;
	this->height = height;

	DXGI_SWAP_CHAIN_DESC sd;
    ZeroMemory(&sd, sizeof(sd));
    sd.BufferCount = 1;
    sd.BufferDesc.Width = width;
    sd.BufferDesc.Height = height;
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.BufferDesc.RefreshRate.Numerator = refresh_rate;
    sd.BufferDesc.RefreshRate.Denominator = 1;
	sd.BufferDesc.Scaling = DXGI_MODE_SCALING_CENTERED;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.OutputWindow = window;
    sd.SampleDesc.Count = 1;
    sd.SampleDesc.Quality = 0;
    sd.Windowed = !fullscreen;
	sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
	sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

	D3D10_ERR(D3D10CreateDeviceAndSwapChain(NULL, D3D10_DRIVER_TYPE_HARDWARE, NULL, deviceFlags, 
		D3D10_SDK_VERSION, &sd, &swap_chain, &d3d_device));

	// Create a render target view
    ID3D10Texture2D* back_buffer;
	D3D10_ERR(swap_chain->GetBuffer(0, __uuidof(ID3D10Texture2D), (LPVOID*)&back_buffer)); 

    D3D10_ERR(d3d_device->CreateRenderTargetView(back_buffer, NULL, &render_target_view));
    back_buffer->Release();

	// Create depth stencil texture
    D3D10_TEXTURE2D_DESC desc_depth;
    desc_depth.Width = width;
    desc_depth.Height = height;
    desc_depth.MipLevels = 1;
    desc_depth.ArraySize = 1;
    desc_depth.Format = DXGI_FORMAT_D32_FLOAT;
    desc_depth.SampleDesc.Count = 1;
    desc_depth.SampleDesc.Quality = 0;
    desc_depth.Usage = D3D10_USAGE_DEFAULT;
    desc_depth.BindFlags = D3D10_BIND_DEPTH_STENCIL;
    desc_depth.CPUAccessFlags = 0;
    desc_depth.MiscFlags = 0;
    D3D10_ERR(d3d_device->CreateTexture2D(&desc_depth, NULL, &depth_stencil));

    // Create the depth stencil view
    D3D10_DEPTH_STENCIL_VIEW_DESC desc_depth_view;
    desc_depth_view.Format = desc_depth.Format;
    desc_depth_view.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2D;
    desc_depth_view.Texture2D.MipSlice = 0;
    D3D10_ERR(d3d_device->CreateDepthStencilView(depth_stencil, &desc_depth_view, &depth_stencil_view));

    d3d_device->OMSetRenderTargets(1, &render_target_view, depth_stencil_view);

    // Setup the viewport
    D3D10_VIEWPORT vp;
    vp.Width = width;
    vp.Height = height;
    vp.MinDepth = 0.0f;
    vp.MaxDepth = 1.0f;
    vp.TopLeftX = 0;
    vp.TopLeftY = 0;
    d3d_device->RSSetViewports(1, &vp);

	// Load effect
	ID3D10Blob* errors;
	uint effectFlags = 0;
#ifdef _DEBUG
	effectFlags |= D3D10_SHADER_DEBUG;
#endif
	D3D10_ERR(D3DX10CreateEffectFromFile(RESOURCE_FILENAME, NULL, NULL, "fx_4_0", effectFlags, 0, d3d_device, 
		NULL, NULL, &effect, &errors, NULL)); 
	render_rects = effect->GetTechniqueByIndex(0);

	effect_texture = effect->GetVariableByName("rect_texture")->AsShaderResource();

	// Create vertex buffer for rects
	D3D10_BUFFER_DESC rects_vb_desc;
	rects_vb_desc.ByteWidth = MAX_QUADS * sizeof(D3D10RectVertex);
	rects_vb_desc.Usage = D3D10_USAGE_DYNAMIC;
	rects_vb_desc.BindFlags = D3D10_BIND_VERTEX_BUFFER;
	rects_vb_desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
	rects_vb_desc.MiscFlags = 0;

	D3D10_ERR(d3d_device->CreateBuffer(&rects_vb_desc, NULL, &rects_vb));

	// Create vertex buffer layout
	D3D10_INPUT_ELEMENT_DESC layout[] =
	{
		{ "SOURCE", 0, DXGI_FORMAT_R32G32B32A32_FLOAT , 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
		{ "DESTINATION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT , 0, 16, D3D10_INPUT_PER_VERTEX_DATA, 0 },
		{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT , 0, 32, D3D10_INPUT_PER_VERTEX_DATA, 0 },
		{ "DEPTH", 0, DXGI_FORMAT_R32_FLOAT, 0, 48, D3D10_INPUT_PER_VERTEX_DATA, 0 },
		{ "ROTATION", 0, DXGI_FORMAT_R32_FLOAT, 0, 52, D3D10_INPUT_PER_VERTEX_DATA, 0 },
	};
	
	D3D10_PASS_DESC pass_description;
	D3D10_ERR(render_rects->GetPassByIndex(0)->GetDesc(&pass_description));
	D3D10_ERR(d3d_device->CreateInputLayout(layout, sizeof(layout)/sizeof(layout[0]), pass_description.pIAInputSignature, 
		pass_description.IAInputSignatureSize, &rects_vb_layout));

	// Allocate space for rect buffer
	rects.reserve(MAX_QUADS);

	D3D10Rect dummy;
	rects.assign(MAX_QUADS, dummy);
}

D3D10Graphics::~D3D10Graphics()
{
	
	if(d3d_device) d3d_device->ClearState();

	if(rects_vb) rects_vb->Release();
	if(rects_vb_layout) rects_vb_layout->Release();

	if(effect) effect->Release();
	if(depth_stencil_view) depth_stencil_view->Release();
	if(depth_stencil) depth_stencil->Release();
    if(render_target_view) render_target_view->Release();
    if(swap_chain) swap_chain->Release();
    if(d3d_device) d3d_device->Release();
}

Texture* D3D10Graphics::loadTexture(string filename)
{
	D3D10Texture* texture = new D3D10Texture(d3d_device, filename);
	return (Texture*)texture;
}

string D3D10Graphics::getDescription()
{
	return string("Direct3D 10.0");
};

void D3D10Graphics::clear(const Color& color)
{
	rect_count = 0;

	float clear_color[4] = { color.r, color.g, color.b, color.a };
	d3d_device->ClearRenderTargetView(render_target_view, clear_color);
	d3d_device->ClearDepthStencilView(depth_stencil_view, D3D10_CLEAR_DEPTH, 1.0f, 0);
}

void D3D10Graphics::present()
{
	// Sort rects
	std::sort(rects.begin(), rects.begin() + rect_count);

	// Build texture spans
	texture_spans.clear();
	
	uint i = 0;
	while(i < rect_count)
	{
		uint count = 1;
		if(i < rect_count-1)
		{
			while(rects[i].texture == rects[i + count].texture)
			{
				count++;
				if(i + count >= rect_count)
					break;
			}
		}

		D3D10TSpan new_span;
		new_span.first_rect = i;
		new_span.count = count;

		texture_spans.push_back(new_span);

		i += count;
	}

	// Fill vertex buffer
	void* data;
	D3D10_ERR(rects_vb->Map(D3D10_MAP_WRITE_DISCARD, 0, &data));
	D3D10RectVertex* vb = (D3D10RectVertex*)data;
	for(uint i = 0; i < rect_count; ++i)
	{
		vb[i] = rects[i].vertex;
	}
	rects_vb->Unmap();

	

	// Draw
	uint stride = sizeof(D3D10RectVertex);
	uint offset = 0;

	d3d_device->IASetVertexBuffers(0, 1, &rects_vb, &stride, &offset);
	d3d_device->IASetInputLayout(rects_vb_layout);
	d3d_device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_POINTLIST);
	
	//render_rects->GetPassByIndex(0)->Apply(0);

	for(uint i = 0; i < texture_spans.size(); ++i)
	{
		D3D10Texture* texture = rects[texture_spans[i].first_rect].texture;
		effect_texture->SetResource(texture->getResourceView());
		render_rects->GetPassByIndex(0)->Apply(0);

		d3d_device->Draw(texture_spans[i].count, texture_spans[i].first_rect);
	}

	swap_chain->Present(1, 0);
	
	rect_count = 0;
}

void D3D10Graphics::draw(Texture* texture, int layer, const RectI& source, const VectorI& dest, const Color& tint)
{
	draw(texture, layer, source, RectI(dest.x, dest.y, 0, 0), tint);
}

void D3D10Graphics::draw(Texture* texture, int layer, const RectI& source, const RectI& dest, const Color& tint)
{
	D3D10Rect new_rect;

	new_rect.texture = static_cast<D3D10Texture*>(texture);
	new_rect.alpha = true;

	bool full_source = source.max.x == 0 && source.max.y == 0;
	bool full_dest = dest.max.x == 0 && dest.max.y == 0;

	float t_width = (float)texture->getWidth();
	float t_height = (float)texture->getHeight();

	int source_m_x, source_m_y;

	if(full_source)
	{
		source_m_x = texture->getWidth();
		source_m_y = texture->getHeight();
	}
	else
	{
		source_m_x = source.max.x;
		source_m_y = source.max.y;
	}

	new_rect.vertex.color = D3DXVECTOR4(tint.r, tint.g, tint.b, tint.a);
	
	new_rect.vertex.source = D3DXVECTOR4(
	(float)source.min.x / t_width, 
	(float)source.min.y / t_height, 
	(float)source_m_x / t_width, 
	(float)source_m_y / t_height
	);

	if(!full_dest)
		new_rect.vertex.dest = D3DXVECTOR4(
		(float)dest.min.x / (float)width, 
		(float)dest.min.y / (float)height,
		(float)dest.max.x / (float)width, 
		(float)dest.max.y / (float)height
		);
	else
		new_rect.vertex.dest = D3DXVECTOR4(
		(float)dest.min.x / (float)width, 
		(float)dest.min.y / (float)height, 
		(float)dest.min.x / (float)width + ((float)(source_m_x - source.min.x) / (float)width), 
		(float)dest.min.y / (float)height + ((float)(source_m_y - source.min.y) / (float)height)
		);

	new_rect.vertex.depth = (float)layer / 65536.0f;
	new_rect.vertex.rotation = 0.0f;

	rects[rect_count++] = new_rect;

	if(rect_count >= MAX_QUADS)
		criticalError("Too many rects!");
}

void D3D10Graphics::draw(Texture* texture, int layer, const RectI& source, const VectorI& dest)
{
	draw(texture, layer, source, RectI(dest.x, dest.y, 0, 0), Color(1.0f, 1.0f, 1.0f));
}

void D3D10Graphics::draw(Texture* texture, int layer, const RectI& source, const RectI& dest)
{
	draw(texture, layer, source, dest, Color(1.0f, 1.0f, 1.0f));
}

} // namespace